<template>
  <div class="json-schema-editor">
    <a-row class="row" :gutter="10">
      <a-col
        :span="toolNum == 2 ? 5 : toolNum == 1 ? 7 : 8"
        class="ant-col-name"
      >
        <div :style="{ marginLeft: `${20 * deep}px` }" class="ant-col-name-c">
          <!-- 展开 -->
          <a-button
            v-if="pickValue.type === 'object'"
            type="link"
            :icon="hidden ? 'caret-right' : 'caret-down'"
            style="color: rgba(0, 0, 0, 0.65)"
            @click="hidden = !hidden"
          />
          <!-- 子集tab -->
          <span v-else style="width: 32px; display: inline-block"></span>
          <!-- key的输入框 -->
          <a-input
            :disabled="disabled || root"
            :value="pickKey"
            class="ant-col-name-input"
            @blur="onInputName"
          />
        </div>
        <!-- 复选框 -->
        <a-tooltip v-if="root">
          <span slot="title" v-text="local['checked_all']">全选</span>
          <a-checkbox
            :disabled="!isObject && !isArray"
            class="ant-col-name-required"
            @change="onRootCheck"
          />
        </a-tooltip>
        <a-tooltip v-else>
          <span slot="title" v-text="local['required']">是否必填</span>
          <a-checkbox
            :disabled="isItem"
            :checked="checked"
            class="ant-col-name-required"
            @change="onCheck"
          />
        </a-tooltip>
      </a-col>
      <!-- 类型选择 -->
      <a-col :span="2">
        <a-select
          v-model="pickValue.type"
          :disabled="disabledType"
          class="ant-col-type"
          @change="onChangeType"
          :getPopupContainer="
            (triggerNode) => {
              return triggerNode.parentNode || document.body
            }
          "
        >
          <a-select-option :key="t" v-for="t in TYPE_NAME">
            {{ t }}
          </a-select-option>
        </a-select>
      </a-col>
      <!-- mock -->
      <a-col :span="4" v-if="isMock">
        <AutoComplete
          :placeholder="local['mock']"
          :dataSource="mockData"
          :disabled="pickValue.type === 'object' || pickValue.type === 'array'"
          :backfill="true"
          optionLabelProp="value"
          @change="mockChange"
        >
          <template slot="default">
            <a-input
              v-model="pickValue['mock']"
              :placeholder="local['mock']"
              class="ant-col-title"
              @change="mockChange"
            >
              <a-icon
                type="edit"
                slot="addonAfter"
                @click="mock_modal_show = true"
                class="editIcon"
              />
            </a-input>
          </template>
        </AutoComplete>
      </a-col>
      <!-- title -->
      <a-col :span="4" v-if="showTitle">
        <a-input
          v-model="pickValue.title"
          :placeholder="local['title']"
          class="ant-col-title"
        >
          <a-icon
            type="edit"
            slot="addonAfter"
            @click="title_modal_show = true"
            class="editIcon"
          />
        </a-input>
      </a-col>
      <!-- 备注 -->
      <a-col :span="toolNum == 2 ? 6 : toolNum == 1 ? 8 : 11">
        <!-- <a-input
          v-model="pickValue.title"
          class="ant-col-title"
          :placeholder="local['title']"
        /> -->
        <a-input
          v-model="pickValue.description"
          :placeholder="local['enum_desc']"
          class="ant-col-title"
        >
          <a-icon
            type="edit"
            slot="addonAfter"
            @click="onInputDesc"
            class="editIcon"
          />
        </a-input>
      </a-col>
      <a-col :span="3" class="ant-col-setting">
        <!-- 设置 -->
        <a-tooltip>
          <span slot="title" v-text="local['adv_setting']">高级设置</span>
          <a-button
            type="link"
            icon="setting"
            class="setting-icon"
            @click="onSetting"
          />
        </a-tooltip>
        <!-- 添加 -->
        <a-tooltip v-if="isObject">
          <span slot="title" v-text="local['add_child_node']">添加子节点</span>
          <a-button
            type="link"
            icon="plus"
            class="plus-icon"
            @click="addChild"
          />
        </a-tooltip>
        <!-- 删除 -->
        <a-tooltip v-if="!root && !isItem">
          <span slot="title" v-text="local['remove_node']">删除节点</span>
          <a-button
            type="link"
            class="close-icon ant-btn-icon-only"
            @click="removeNode"
          >
            <i aria-label="icon: plus" class="anticon anticon-plus">
              <svg
                viewBox="64 64 896 896"
                data-icon="plus"
                width="1em"
                height="1em"
                fill="currentColor"
                aria-hidden="true"
                focusable="false"
                class=""
              >
                <path
                  d="M810.666667 273.493333L750.506667 213.333333 512 451.84 273.493333 213.333333 213.333333 273.493333 451.84 512 213.333333 750.506667 273.493333 810.666667 512 572.16 750.506667 810.666667 810.666667 750.506667 572.16 512z"
                  p-id="1142"
                ></path>
              </svg>
            </i>
          </a-button>
        </a-tooltip>
      </a-col>
    </a-row>
    <!-- 最外层的子集 -->
    <template v-if="!hidden && pickValue.properties && !isArray">
      <json-schema-editor
        v-for="(item, key, index) in pickValue.properties"
        :value="{ [key]: item }"
        :parent="pickValue"
        :key="index"
        :deep="deep + 1"
        :root="false"
        class="children"
        :lang="lang"
        :custom="custom"
        :isMock="isMock"
        :showTitle="showTitle"
      />
    </template>
    <!-- 数组特定 -->
    <template v-if="isArray">
      <json-schema-editor
        :value="{ items: pickValue.items }"
        :deep="deep + 1"
        disabled
        isItem
        :root="false"
        class="children"
        :lang="lang"
        :custom="custom"
        :isMock="isMock"
        :showTitle="showTitle"
      />
    </template>
    <!-- 设置弹窗 -->
    <a-modal
      v-model="modalVisible"
      v-if="modalVisible"
      :title="local['adv_setting']"
      :maskClosable="false"
      :okText="local['ok']"
      :cancelText="local['cancel']"
      width="800px"
      @ok="handleOk"
      dialogClass="json-schema-editor-advanced-modal"
    >
      <!-- <h3 v-text="local['base_setting']">基础设置</h3> -->
      <!-- <a-form v-model="advancedValue" class="ant-advanced-search-form">
        <a-row :gutter="6">
          <a-col :span="8" v-for="(item, key) in advancedValue" :key="key">
            <a-form-item>
              <span>{{ local[key] }}</span>
              <a-input-number
                v-model="advancedValue[key]"
                v-if="advancedAttr[key].type === 'integer'"
                style="width: 100%"
                :placeholder="key"
              />
              <a-input-number
                v-model="advancedValue[key]"
                v-else-if="advancedAttr[key].type === 'number'"
                style="width: 100%"
                :placeholder="key"
              />
              <span
                v-else-if="advancedAttr[key].type === 'boolean'"
                style="display: inline-block; width: 100%"
              >
                <a-switch v-model="advancedValue[key]" />
              </span>
              <a-select
                v-else-if="advancedAttr[key].type === 'array'"
                v-model="advancedValue[key]"
                style="width: 100%"
                :getPopupContainer="
                  (triggerNode) => {
                    return triggerNode.parentNode || document.body
                  }
                "
                :placeholder="local[key]"
              >
                <a-select-option value="">{{
                  local['nothing']
                }}</a-select-option>
                <a-select-option :key="t" v-for="t in advancedAttr[key].enums">
                  {{ t }}
                </a-select-option>
              </a-select>

              <a-input
                v-model="advancedValue[key]"
                v-else
                style="width: 100%"
                :placeholder="key"
              />
            </a-form-item>
          </a-col>
        </a-row>
      </a-form> -->
      <!-- <h3 v-text="local['add_custom']" v-show="custom">添加自定义属性</h3>
      <a-form class="ant-advanced-search-form" v-show="custom">
        <a-row :gutter="6">
          <a-col :span="8" v-for="item in customProps" :key="item.key">
            <a-form-item :label="item.key">
              <a-input v-model="item.value" style="width: calc(100% - 30px)" />
              <a-button
                icon="close"
                type="link"
                @click="customProps.splice(customProps.indexOf(item), 1)"
                style="width: 30px"
              ></a-button>
            </a-form-item>
          </a-col>
          <a-col :span="8" v-show="addProp.key != undefined">
            <a-form-item>
              <a-input
                slot="label"
                v-model="addProp.key"
                style="width: 100px"
              />
              <a-input v-model="addProp.value" style="width: 100%" />
            </a-form-item>
          </a-col>
          <a-col :span="8">
            <a-form-item>
              <a-button
                icon="check"
                type="link"
                @click="confirmAddCustomNode(null)"
                v-if="customing"
              ></a-button>
              <a-tooltip :title="local['add_custom']" v-else>
                <a-button
                  icon="plus"
                  type="link"
                  @click="addCustomNode"
                ></a-button>
              </a-tooltip>
            </a-form-item>
          </a-col>
        </a-row>
      </a-form> -->
      <!-- <h3 v-text="local['preview']">预览</h3>
      <pre style="width: 100%">{{ completeNodeValue }}</pre> -->
      <template v-if="isArray">
        <h3 v-text="local['base_setting']">基础设置</h3>
        <a-form
          v-model="advancedValue"
          class="ant-advanced-search-form"
          :label-col="{ span: 6 }"
          labelAlign="right"
        >
          <a-form-item>
            <a-row :gutter="6">
              <a-col :span="6" class="label_right">
                <span>uniqueItems</span>
                <a-tooltip title="开启后, 每个元素都不相同">
                  <a-icon style="margin-left: 5px" type="question-circle" /> :
                </a-tooltip>
              </a-col>
              <a-col :span="18">
                <a-switch v-model="advancedValue.uniqueItems" />
              </a-col>
            </a-row>
          </a-form-item>
          <a-row>
            <a-col :span="12">
              <a-form-item>
                <a-row>
                  <a-col
                    :span="12"
                    class="label_right"
                    style="padding-right: 8px"
                    >最小元素个数 :</a-col
                  >
                  <a-col :span="12">
                    <a-input-number
                      v-model="advancedValue.minItems"
                      :placeholder="'minItems'"
                    />
                  </a-col>
                </a-row>
              </a-form-item>
            </a-col>
            <a-col :span="12">
              <a-form-item>
                <a-row>
                  <a-col
                    :span="12"
                    class="label_right"
                    style="padding-right: 8px"
                    >最大元素个数 :</a-col
                  >
                  <a-col :span="12">
                    <a-input-number
                      v-model="advancedValue.maxItems"
                      :placeholder="'maxItems'"
                    />
                  </a-col>
                </a-row>
              </a-form-item>
            </a-col>
          </a-row>
        </a-form>
      </template>
      <h3 v-text="local['all_setting']">编辑源码</h3>
      <Codemirror v-model="codeValue" :readOnly="false"></Codemirror>
    </a-modal>
    <!-- 备注弹窗 -->
    <a-modal
      v-model="desc_modal_show"
      v-if="desc_modal_show"
      :title="local['enum_desc']"
      :maskClosable="false"
      :okText="local['ok']"
      :cancelText="local['cancel']"
      width="800px"
      @ok="desc_modal_show = false"
      dialogClass="desc-modal"
    >
      <a-textarea
        v-model="pickValue.description"
        :placeholder="local['enum_desc']"
        :auto-size="{ minRows: 3, maxRows: 5 }"
      />
    </a-modal>
    <!-- mock弹窗 -->
    <a-modal
      v-model="mock_modal_show"
      v-if="mock_modal_show"
      :title="local['mock']"
      :maskClosable="false"
      :okText="local['ok']"
      :cancelText="local['cancel']"
      width="520px"
      @ok="modalOk('mock', 'mockModalValue')"
      dialogClass="desc-modal"
    >
      <a-textarea
        v-model="mockModalValue"
        :placeholder="local['mock']"
        :auto-size="{ minRows: 3, maxRows: 5 }"
      />
    </a-modal>
    <!-- title弹窗 -->
    <a-modal
      v-model="title_modal_show"
      v-if="title_modal_show"
      :title="local['title']"
      :maskClosable="false"
      :okText="local['ok']"
      :cancelText="local['cancel']"
      width="520px"
      @ok="modalOk('title', 'titleModalValue')"
      dialogClass="desc-modal"
    >
      <a-textarea
        v-model="titleModalValue"
        :placeholder="local['title']"
        :auto-size="{ minRows: 3, maxRows: 5 }"
      />
    </a-modal>
  </div>
</template>
<script>
import Vue from 'vue'
import { isNull } from './util'
import { TYPE_NAME, TYPE } from './type/type'

import {
  Row,
  Col,
  Button,
  Input,
  InputNumber,
  Icon,
  Checkbox,
  Select,
  Tooltip,
  Modal,
  Form,
  Switch,
  Textarea,
  AutoComplete,
} from 'ant-design-vue'
import LocalProvider from './LocalProvider'
import Codemirror from '../Codemirror.vue'
Modal.install(Vue)
export default {
  name: `JsonSchemaEditor`,
  components: {
    Codemirror,
    ARow: Row,
    ACol: Col,
    AButton: Button,
    // eslint-disable-next-line vue/no-unused-components
    AIcon: Icon,
    AInput: Input,
    // APopover: Popover,
    AInputNumber: InputNumber,
    ACheckbox: Checkbox,
    ASelect: Select,
    ASelectOption: Select.Option,
    ATooltip: Tooltip,
    AModal: Modal,
    AForm: Form,
    AFormItem: Form.Item,
    ASwitch: Switch,
    ATextarea: Input.TextArea,
    AutoComplete,
  },
  props: {
    value: {
      type: Object,
      required: true,
    },
    disabled: {
      //name不可编辑，根节点name不可编辑,数组元素name不可编辑
      type: Boolean,
      default: false,
    },
    disabledType: {
      //禁用类型选择
      type: Boolean,
      default: false,
    },
    isItem: {
      //是否数组元素
      type: Boolean,
      default: false,
    },
    deep: {
      // 节点深度，根节点deep=0
      type: Number,
      default: 0,
    },
    root: {
      //是否root节点
      type: Boolean,
      default: true,
    },
    parent: {
      //父节点
      type: Object,
      default: null,
    },
    custom: {
      //enable custom properties
      type: Boolean,
      default: false,
    },
    lang: {
      // i18n language
      type: String,
      default: `zh_CN`,
    },
    showTitle: {
      type: Boolean,
      default: false,
    },
    isMock: {
      type: Boolean,
      default: false,
    },
  },
  computed: {
    pickValue() {
      return Object.values(this.value)[0]
    },
    pickKey() {
      return Object.keys(this.value)[0]
    },
    isObject() {
      return this.pickValue.type === `object`
    },
    isArray() {
      return this.pickValue.type === `array`
    },
    checked() {
      return (
        this.parent &&
        this.parent.required &&
        this.parent.required.indexOf(this.pickKey) >= 0
      )
    },
    advanced() {
      return TYPE[this.pickValue.type]
    },
    advancedAttr() {
      return TYPE[this.pickValue.type].attr
    },
    advancedNotEmptyValue() {
      const jsonNode = Object.assign({}, this.advancedValue)
      for (let key in jsonNode) {
        isNull(jsonNode[key]) && delete jsonNode[key]
      }
      return jsonNode
    },
    completeNodeValue() {
      const t = {}
      for (const item of this.customProps) {
        t[item.key] = item.value
      }
      const code = Object.assign(
        {},
        this.pickValue,
        this.advancedNotEmptyValue,
        t
      )
      return code
    },
    codeValue: {
      get: function () {
        return JSON.stringify(this.completeNodeValue, null, 2)
      },
      set: function (newVal) {
        this.completeNodeValue = JSON.parse(newVal)
      },
    },
  },
  data() {
    return {
      TYPE_NAME,
      hidden: false,
      countAdd: 1,
      modalVisible: false,
      advancedValue: {},
      addProp: {}, // 自定义属性
      customProps: [],
      customing: false,
      local: LocalProvider(this.lang),
      desc_modal_show: false,
      toolNum: 0,
      mock_modal_show: false,
      title_modal_show: false,
      mockData: [
        {
          value: `@string`,
          text: `@string`,
        },
        {
          value: `@natural`,
          text: `@natural`,
        },
        {
          value: `@float`,
          text: `@float`,
        },
        {
          value: `@character`,
          text: `@character`,
        },
        {
          value: `@boolean`,
          text: `@boolean`,
        },
        {
          value: `@url`,
          text: `@url`,
        },
        {
          value: `@domain`,
          text: `@domain`,
        },
        {
          value: `@ip`,
          text: `@ip`,
        },
        {
          value: `@id`,
          text: `@id`,
        },
        {
          value: `@guid`,
          text: `@guid`,
        },
        {
          value: `@now`,
          text: `@now`,
        },
        {
          value: `@timestamp`,
          text: `@timestamp`,
        },
      ],
      mockModalValue: ``,
      titleModalValue: ``,
      descModalValue: ``,
    }
  },
  watch: {
    mock_modal_show(val) {
      if (val) {
        this.mockModalValue = this.pickValue.mock
      }
    },
    title_modal_show(val) {
      if (val) {
        console.log(this.pickValue)
        this.titleModalValue = this.pickValue.title
      }
    },
  },
  methods: {
    modalOk(key, modalKey) {
      if (!this.pickValue[key]) {
        this.$set(this.pickValue, key, this[modalKey])
      } else {
        this.pickValue[key] = this[modalKey]
      }
      this[`${key}_modal_show`] = false
      this.$forceUpdate()
    },
    mockChange(value) {
      if (!this.pickValue.mock) {
        this.$set(this.pickValue, `mock`, value)
      } else {
        this.pickValue.mock = value
      }
    },
    parseCustomProps() {
      const ownProps = [
        `type`,
        `title`,
        `properties`,
        `items`,
        ...Object.keys(this.advancedAttr),
      ]
      Object.keys(this.pickValue).forEach((key) => {
        ownProps.indexOf(key) === -1 &&
          this.confirmAddCustomNode({ key: key, value: this.pickValue[key] })
      })
    },
    onInputName(e) {
      const oldKey = this.pickKey
      const newKey = e.target.value
      if (oldKey === newKey) return

      const nodeValue = this.parent.properties[oldKey]

      // 替换key名
      this.$delete(this.parent.properties, oldKey)
      this.$set(this.parent.properties, newKey, nodeValue)

      // required重新设置
      const requireds = this.parent.required || []
      const oldIndex = requireds.indexOf(oldKey)
      if (requireds.length > 0 && oldIndex > -1) {
        requireds.splice(oldIndex, 1)
        requireds.push(newKey)
        this.$set(this.parent, `required`, [...new Set(requireds)])
      }
    },
    onChangeType() {
      this.$delete(this.pickValue, `properties`)
      this.$delete(this.pickValue, `items`)
      this.$delete(this.pickValue, `required`)
      this.$delete(this.pickValue, `format`)
      if (this.isArray) {
        this.$set(this.pickValue, `items`, { type: `string` })
      }
    },
    onCheck(e) {
      this._checked(e.target.checked, this.parent)
    },
    onRootCheck(e) {
      this._deepCheck(e.target.checked, this.pickValue)
    },
    _deepCheck(checked, node) {
      if (node.type === `object` && node.properties) {
        checked
          ? this.$set(node, `required`, Object.keys(node.properties))
          : this.$delete(node, `required`)
        Object.keys(node.properties).forEach((key) =>
          this._deepCheck(checked, node.properties[key])
        )
      } else if (node.type === `array` && node.items.type === `object`) {
        checked
          ? this.$set(
              node.items,
              `required`,
              Object.keys(node.items.properties)
            )
          : this.$delete(node.items, `required`)
        Object.keys(node.items.properties).forEach((key) =>
          this._deepCheck(checked, node.items.properties[key])
        )
      }
    },
    _checked(checked, parent) {
      let required = parent.required
      if (checked) {
        required || this.$set(this.parent, `required`, [])

        required = this.parent.required
        required.indexOf(this.pickKey) === -1 && required.push(this.pickKey)
      } else {
        const pos = required.indexOf(this.pickKey)
        pos >= 0 && required.splice(pos, 1)
      }
      required.length === 0 && this.$delete(parent, `required`)
    },
    addChild() {
      const name = this._joinName()
      const type = `string`
      const node = this.pickValue
      node.properties || this.$set(node, `properties`, {})
      const props = node.properties
      this.$set(props, name, { type: type })
    },
    addCustomNode() {
      this.$set(this.addProp, `key`, this._joinName())
      this.$set(this.addProp, `value`, ``)
      this.customing = true
    },
    confirmAddCustomNode(prop) {
      const p = prop || this.addProp
      this.customProps.push(p)
      this.addProp = {}
      this.customing = false
    },
    removeNode() {
      const { properties, required } = this.parent
      this.$delete(properties, this.pickKey)
      if (required) {
        const pos = required.indexOf(this.pickKey)
        pos >= 0 && required.splice(pos, 1)
        required.length === 0 && this.$delete(this.parent, `required`)
      }
    },
    _joinName() {
      return `field_${this.deep}_${this.countAdd++}`
    },
    onSetting() {
      this.modalVisible = true
      this.advancedValue = this.advanced.value
      for (const k in this.advancedValue) {
        if (this.pickValue[k]) this.advancedValue[k] = this.pickValue[k]
      }
      // console.log(this.advancedValue)
      this.parseCustomProps()
    },
    onInputDesc() {
      this.desc_modal_show = true
    },
    handleOk() {
      this.modalVisible = false
      for (const key in this.advancedValue) {
        if (isNull(this.advancedValue[key])) {
          this.$delete(this.pickValue, key)
        } else {
          this.$set(this.pickValue, key, this.advancedValue[key])
        }
      }
      for (const item of this.customProps) {
        this.$set(this.pickValue, item.key, item.value)
      }
    },
  },
  created() {
    if (this.isMock) {
      this.toolNum += 1
    }
    if (this.showTitle) {
      this.toolNum += 1
    }
  },
}
</script>
<style lang="less" scoped>
.json-schema-editor {
  .row {
    display: flex;
    margin: 12px;
    .ant-col-name {
      display: flex;
      align-items: center;
      .ant-col-name-c {
        display: flex;
        align-items: center;
        width: 100%;
      }
      .ant-col-name-required {
        flex: 0 0 24px;
        text-align: center;
      }
    }
    .ant-col-type {
      width: 100%;
    }
    .ant-col-setting {
      display: inline-block;
    }
    .json-schema-editor {
      .row {
        .setting-icon {
          color: rgba(0, 0, 0, 0.45);
          border: none;
        }
      }
    }
    .plus-icon {
      border: none;
    }
    .close-icon {
      color: #888;
      border: none;
    }
  }
}
.label_right {
  display: flex;
  justify-content: flex-end;
}
/deep/ .ant-form-item {
  margin-bottom: 16px;
  height: 30px;
}
.ant-col-name-input {
  width: 100%;
}
.editIcon {
  cursor: pointer;
}
</style>
<style lang="less">
.desc-modal {
  width: 520px !important;
}
.json-schema-editor-advanced-modal,
.desc-modal {
  color: rgba(0, 0, 0, 0.65);
  min-width: 600px;
  pre {
    font-family: monospace;
    height: 100%;
    overflow-y: auto;
    border: 1px solid rgba(0, 0, 0, 0.1);
    border-radius: 4px;
    padding: 12px;
    width: 50%;
  }
  h3 {
    display: block;
    border-left: 3px solid #1890ff;
    padding: 0 8px;
  }
  .ant-advanced-search-form {
    .ant-form-item {
      display: flex;
      .ant-form-item-control-wrapper {
        flex: 1;
      }
    }
  }
}
</style>
